﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Windows.Input;
using Microsoft.Research.DynamicDataDisplay.DataSources;
using Model;
using Model.Interfaces;
using MvvmCore;
using MvvmCore.ViewModel;

namespace Lab1.ViewModel
{
    public class CauchySystemViewModel : BaseViewModel
    {
        #region Fields

        private delegate IEnumerable<Point> RefFunc(ICauchySystem system, out double step); 

        private readonly ICauchySystem _system;
        private ICommand _eulerSolveCommand;
        private ICommand _eulerSolveWithAccuracyCommand;
        private ICommand _modifiedEulerSolveCommand;
        private ICommand _modifiedEulerSolveWithAccuracyCommand;
        private ICommand _rungeKuttSolveCommand;
        private ICommand _rungeKuttSolveWithAccuracyCommand;
        private ObservableCollection<Point> _solution;
        private CompositeDataSource _source;
        private double _step;

        #endregion

        #region Properties

        public double Step
        {
            get { return _step; }
            set
            {
                if (value < 0)
                    throw new ArgumentException("Step < 0");
                _step = value;
                OnPropertyChanged("Step");
            }
        }

        public double A
        {
            get { return _system.A; }
            set
            {
                _system.A = value;
                OnPropertyChanged("A");
            }
        }

        public double B
        {
            get { return _system.B; }
            set 
            { 
                _system.B = value;
                OnPropertyChanged("B");
            }
        }

        public double Epsilon
        {
            get { return _system.Epsilon; }
            set
            {
                _system.Epsilon = value;
                OnPropertyChanged("Epsilon");
            }
        }

        public string Function { get; set; }

        public ObservableCollection<Point> Solution
        {
            get { return _solution; }
            set
            {
                if (value == null)
                    throw new ArgumentNullException();
                _solution = value;
                OnPropertyChanged("Solution");
            }
        }

        public CompositeDataSource PlotSolution
        {
            get { return _source; }
            set
            {
                if (value == null)
                    throw new ArgumentNullException();
                _source = value;
                OnPropertyChanged("PlotSolution");
            }
        }

        public ICommand EulerSolveCommand
        {
            get { return _eulerSolveCommand ?? (_eulerSolveCommand = new RelayCommand(o => EulerSolve())); }
        }

        public ICommand EulerSolveWithAccuracyCommand
        {
            get
            {
                return _eulerSolveWithAccuracyCommand ??
                       (_eulerSolveWithAccuracyCommand = new RelayCommand(o => EulerSolveWithAccuracy()));
            }
        }

        public ICommand ModifiedEulerSolveCommand
        {
            get { return _modifiedEulerSolveCommand ?? (_modifiedEulerSolveCommand = new RelayCommand(o => ModifiedEulerSolve())); }
        }

        public ICommand ModifiedEulerSolveWithAccuracyCommand
        {
            get
            {
                return _modifiedEulerSolveWithAccuracyCommand ??
                       (_modifiedEulerSolveWithAccuracyCommand = new RelayCommand(o => ModifiedEulerSolveWithAccuracy()));
            }
        }

        public ICommand RungeKuttSolveCommand
        {
            get { return _rungeKuttSolveCommand ?? (_rungeKuttSolveCommand = new RelayCommand(o => RungeKuttSolve())); }
        }

        public ICommand RungeKuttSolveWithAccuracyCommand
        {
            get
            {
                return _rungeKuttSolveWithAccuracyCommand ??
                       (_rungeKuttSolveWithAccuracyCommand = new RelayCommand(o => RungeKuttSolveWithAccuracy()));
            }
        }

        #endregion

        #region Constructors

        public CauchySystemViewModel(ICauchySystem system)
        {
            _system = system;
            Solution = new ObservableCollection<Point>();
            PlotSolution = GetCompositeDataSource(Solution);
        }

        #endregion

        #region Methods


        private static CompositeDataSource GetCompositeDataSource(IEnumerable<Point> points)
        {
            var dataSource = new CompositeDataSource();
            var enumerable = points as List<Point> ?? points.ToList();
            
            var xData = new EnumerableDataSource<double>(enumerable.Select(point => point.X));
            xData.SetXMapping(x => x);
            var yData = new EnumerableDataSource<double>(enumerable.Select(point => point.Y));
            yData.SetYMapping(y => y);
            dataSource = xData.Join(yData);
            return dataSource;
        }


        protected void EulerSolve()
        {
            Solve(SolutionMethod.EulerSolution);
        }

        protected void EulerSolveWithAccuracy()
        {
            SolveWithAccuracy(SolutionMethod.EulerSolutionWithAccuracy);
        }

        protected void ModifiedEulerSolve()
        {
            Solve(SolutionMethod.ModifiedEulerSolution);
        }

        protected void ModifiedEulerSolveWithAccuracy()
        {
            SolveWithAccuracy(SolutionMethod.ModifiedEulerSolutionWithAccuracy);
        }
        
        protected void RungeKuttSolve()
        {
            Solve(SolutionMethod.RungeKuttSolution);
        }

        protected  void RungeKuttSolveWithAccuracy()
        {
            SolveWithAccuracy(SolutionMethod.RungeKuttSolutionWithAccuracy);
        }

        private void Solve(Func<ICauchySystem, double, IEnumerable<Point>> func)
        {
            if (Solution.Count != 0)
                Solution.Clear();
            foreach (var point in func(_system, Step))
            {
                Solution.Add(point);
            }
            PlotSolution = GetCompositeDataSource(Solution);
        }

        private void SolveWithAccuracy(RefFunc func)
        {
            if (Solution.Count != 0)
                Solution.Clear();
            double step;
            foreach (var point in func(_system, out step))
            {
                Solution.Add(point);
            }
            Step = step;
            PlotSolution = GetCompositeDataSource(Solution);
        }

        #endregion
    }
}
